#include "Shader.h"

#include <iostream>
#include <vector>
#include <stdlib.h>

Shader::Shader() 
{
}

Shader::Shader(const string& filename) :
m_filename(filename)
{
    init(m_filename);
}

bool Shader::init(const string& filename) 
{
    m_vertexShader = glCreateShader(GL_VERTEX_SHADER);
	m_fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);

	const char* vsText = readFile("Shaders/" + filename + ".vert");
	const char* fsText = readFile("Shaders/" + filename + ".frag");	
    
    if (vsText == NULL || fsText == NULL) 
	{
		std::cerr << "Either vertex shader or fragment shader file not found." << std::endl;
        return false;
    }
    
	glShaderSource(m_vertexShader, 1, &vsText, 0);
    glCompileShader(m_vertexShader);

    if (!isShaderOk(m_vertexShader))
    {
        return false;
    }

	glShaderSource(m_fragmentShader, 1, &fsText, 0);
    glCompileShader(m_fragmentShader);

    if (!isShaderOk(m_fragmentShader))
    {
        return false;
    }

	m_shaderId = glCreateProgram();
	glAttachShader(m_shaderId, m_vertexShader);
	glAttachShader(m_shaderId, m_fragmentShader);
    glBindFragDataLocation(m_shaderId, 0, "outColor");
    glBindFragDataLocation(m_shaderId, 1, "outDepth");
    glBindAttribLocation(m_shaderId, POSITION_ATTRIBUTE, "position");
	glBindAttribLocation(m_shaderId, NORMAL_ATTRIBUTE, "normal");
    glBindAttribLocation(m_shaderId, TEXCOORD_ATTRIBUTE, "texcoord");
    glBindAttribLocation(m_shaderId, TANGENT_ATTRIBUTE, "tangent");
    glBindAttribLocation(m_shaderId, BITANGENT_ATTRIBUTE, "bitangent");
	glLinkProgram(m_shaderId);

    if (!isProgramOk(m_shaderId))
    {
        return false;
    }

    return true;
}

Shader::~Shader() 
{
	glDetachShader(m_shaderId, m_vertexShader);
	glDetachShader(m_shaderId, m_fragmentShader);    
	glDeleteShader(m_vertexShader);
	glDeleteShader(m_fragmentShader);
	glDeleteProgram(m_shaderId);
}

unsigned int Shader::id() const
{
	return m_shaderId;
}

bool Shader::loadShaders(const string& filename)
{
	const char* vsText = readFile("Shaders/" + filename + ".vert");
	const char* fsText = readFile("Shaders/" + filename + ".frag");	
    
    if (vsText == NULL || fsText == NULL) 
	{
		std::cerr << "Either vertex shader or fragment shader file not found." << std::endl;
        return false;
    }
    
	glShaderSource(m_vertexShader, 1, &vsText, 0);
    glCompileShader(m_vertexShader);

    if (!isShaderOk(m_vertexShader))
    {
        return false;
    }

	glShaderSource(m_fragmentShader, 1, &fsText, 0);
    glCompileShader(m_fragmentShader);

    if (!isShaderOk(m_fragmentShader))
    {
        return false;
    }

	glAttachShader(m_shaderId, m_vertexShader);
	glAttachShader(m_shaderId, m_fragmentShader);
    glBindFragDataLocation(m_shaderId, 0, "outColor");
    glBindFragDataLocation(m_shaderId, 1, "outDepth");
    glBindAttribLocation(m_shaderId, POSITION_ATTRIBUTE, "position");
	glBindAttribLocation(m_shaderId, NORMAL_ATTRIBUTE, "normal");
    glBindAttribLocation(m_shaderId, TEXCOORD_ATTRIBUTE, "texcoord");
    glBindAttribLocation(m_shaderId, TANGENT_ATTRIBUTE, "tangent");
    glBindAttribLocation(m_shaderId, BITANGENT_ATTRIBUTE, "bitangent");
	glLinkProgram(m_shaderId);

    if (!isProgramOk(m_shaderId))
    {
        return false;
    }

    return true;
}

std::string Shader::getName() const
{
    return m_filename;
}

char* Shader::readFile(const string& filename) 
{
	char* text = NULL;
    
	if (filename.c_str() != NULL) 
	{
        FILE* file;
        fopen_s(&file, filename.c_str(), "r");
        
		if (file != NULL) 
		{
            fseek(file, 0, SEEK_END);
            int count = ftell(file);
            rewind(file);
            
			if (count > 0) 
			{
				text = (char*)malloc(sizeof(char) * (count + 1));
				count = fread(text, sizeof(char), count, file);
				text[count] = '\0';
			}
			fclose(file);
		}
	}

	return text;
}

bool Shader::isShaderOk(const GLuint shaderId)
{
    GLint result = GL_FALSE;
    GLint logLength = 0;

    glGetShaderiv(shaderId, GL_COMPILE_STATUS, &result);
    glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &logLength);

    if (result == GL_FALSE)
    {
        std::vector<GLchar> errorMsg(logLength+1);
        glGetShaderInfoLog(shaderId, logLength, NULL, &errorMsg[0]);
        printf("%s\n", &errorMsg[0]);
        return false;
    }

    return true;
}

bool Shader::isProgramOk(const GLuint programId)
{
    GLint result = GL_FALSE;
    GLint logLength = 0;

    glGetProgramiv(programId, GL_LINK_STATUS, &result);
    glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &logLength);

    if (result == GL_FALSE)
    {
        std::vector<GLchar> errorMsg(logLength+1);
        glGetShaderInfoLog(programId, logLength, NULL, &errorMsg[0]);
        printf("%s\n", &errorMsg[0]);
        return false;
    }

    return true;
}